package com.core.image.core.impl.async;

import com.core.image.core.IoArgs;
import com.core.image.core.ReceivePacket;
import com.core.image.core.SendPacket;
import com.core.image.core.frames.*;

import java.io.Closeable;
import java.io.IOException;
import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
import java.util.Collection;
import java.util.HashMap;

/**
 * describe:
 *
 * @author 库琦
 * @date 2020/01/30
 */
public class AsyncPacketWriter implements Closeable {
    private PacketProvider packetProvider;
    private final HashMap<Short,PacketModel> packetMap = new HashMap<>();
    private final IoArgs args = new IoArgs();
    private volatile Frame frameTemp;


     AsyncPacketWriter(PacketProvider provider){
        this.packetProvider = provider;
    }

    synchronized void consumeIoArgs(IoArgs ioArgs) {
         if (frameTemp == null){
            Frame temp;
            do {
                temp  = buildNewFrame(args);
            }while (temp == null&&args.remained());
            if (temp == null){
                return;
            }
            frameTemp = temp;
            if (!args.remained()){
                return;
            }
         }
         Frame currentFrame = frameTemp;
         do {
             try {
                if(currentFrame.handle(ioArgs)){
                   if (currentFrame instanceof ReceiveHeaderFrame){
                     ReceiveHeaderFrame headerFrame = (ReceiveHeaderFrame) currentFrame;
                       ReceivePacket packet = packetProvider.takePacket(headerFrame.getPacketType(),
                               headerFrame.getPacketLength(),
                               headerFrame.getPacketHeaderInfo());
                       appendNewPacket(headerFrame.getBodyIdentifier(),packet);
                   }else if (currentFrame instanceof ReceiveEntityFrame){
                     completeEntityFrame((ReceiveEntityFrame)currentFrame);
                   }
                   frameTemp = null;
                   break;
                }
             }catch (Exception e){

             }

         }while (args.remained());
    }

    private void completeEntityFrame(ReceiveEntityFrame frame) {
        synchronized (packetMap) {
        short identifier = frame.getBodyIdentifier();
        int length = frame.getBodyLength();

        PacketModel model = packetMap.get(identifier);
        if (model == null){
            return;
        }
        model.unreceivedLength -= length;
        if (model.unreceivedLength <= 0){
            packetProvider.completedPacket(model.packet,true);
            packetMap.remove(identifier);
        }}
    }

    private void appendNewPacket(short bodyIdentifier, ReceivePacket packet) {
        synchronized (packetMap) {
         PacketModel model = new PacketModel(packet);
         packetMap.put(bodyIdentifier,model);
        }
    }

    private Frame buildNewFrame(IoArgs args) {
        AbsReceiveFrame frame = ReceiveFrameFactory.createInstance(args);
        if (frame instanceof  CancelReceiveFrame){
            cancelReceivePacket(frame.getBodyIdentifier());
            return null;
        }else if (frame instanceof HeartbeatReceiveFrame){
            packetProvider.onReceivedHeartbeat();
        }
        else if (frame instanceof ReceiveEntityFrame){
            WritableByteChannel channel = getPacketChannel(frame.getBodyIdentifier());
            ((ReceiveEntityFrame)frame).bindPacketChannel(channel);

        }
        return frame;
    }

    private WritableByteChannel getPacketChannel(short bodyIdentifier) {
         synchronized (packetMap){
        PacketModel model = packetMap.get(bodyIdentifier);
            return model == null? null: model.channel;
         }
     }

    private void cancelReceivePacket(short bodyIdentifier) {
         synchronized (packetMap){
        PacketModel model = packetMap.get(bodyIdentifier);
        if (model != null){
            ReceivePacket packet = model.packet;
            packetProvider.completedPacket(packet,false);
        }}
    }

    IoArgs takeIoArgs() {
         args.limit(frameTemp== null?
                 Frame.FRAME_HEADER_LENGTH:
                 frameTemp.getConsumableLength());
         return args;
    }

    @Override
    public synchronized void close() throws IOException {
        synchronized (packetMap) {
            Collection<PacketModel> values = packetMap.values();
            for (PacketModel value : values) {
                packetProvider.completedPacket(value.packet, false);
            }
            packetMap.clear();
        }

    }
static class PacketModel{
        final ReceivePacket packet;
        final WritableByteChannel channel;
        volatile long unreceivedLength;
        PacketModel(ReceivePacket<?,?> packet){
            this.packet = packet;
            this.channel = Channels.newChannel(packet.open());
            this.unreceivedLength = packet.length();
        }


}

    interface  PacketProvider{
        ReceivePacket takePacket(byte type,long length,byte[] headerInfo);
        void completedPacket(ReceivePacket packet,boolean isSuccessed);
        /**
         * 当收到一个心跳包时触发
         */
        void onReceivedHeartbeat();
    }
}
